package ua.soso.ui.dwr.extension;

import org.directwebremoting.spring.DwrAnnotationNamespaceHandler;
import org.directwebremoting.spring.DwrController;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExtendedDwrAnnotationNamespaceHandler extends DwrAnnotationNamespaceHandler {
    @Override
    public void init() {
        super.init();

        registerBeanDefinitionParser("controller", new ExtendedControllerBeanDefinitionParser());
    }

    protected static class ClassLoaderDwrController extends DwrController {
        @Override
        public void afterPropertiesSet() throws Exception {
            ClassLoader defaultLoader = Thread.currentThread().getContextClassLoader();

            Thread.currentThread().setContextClassLoader(ClassLoaderDwrController.class.getClassLoader());

            try {
                super.afterPropertiesSet();
            } finally {
                Thread.currentThread().setContextClassLoader(defaultLoader);
            }
        }
    }

    protected static class ExtendedControllerBeanDefinitionParser implements BeanDefinitionParser {
        @Override
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            BeanDefinitionBuilder dwrController = BeanDefinitionBuilder.rootBeanDefinition(ClassLoaderDwrController.class);

            String debug = element.getAttribute("debug");

            if (StringUtils.hasText(debug)) {
                dwrController.addPropertyValue("debug", debug);
            }

            String beanName = element.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
            String nameAttr = element.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE);

            String[] aliases = null;

            if (!StringUtils.hasText(beanName)) {
                beanName = element.getAttribute("name");

                if (!StringUtils.hasText(beanName)) {
                    beanName = "dwrController"; // Offer a sensible default if no id was specified
                }
            } else {
                String aliasName = element.getAttribute("name");

                if (StringUtils.hasText(aliasName)) {
                    aliases = StringUtils.tokenizeToStringArray(nameAttr, BeanDefinitionParserDelegate.BEAN_NAME_DELIMITERS);
                }
            }

            parseControllerChildren(dwrController, element);

            BeanDefinitionHolder holder = new BeanDefinitionHolder(dwrController.getBeanDefinition(), beanName, aliases);
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());

            return dwrController.getBeanDefinition();
        }

        protected void parseControllerChildren(BeanDefinitionBuilder dwrControllerDefinition, Element parent) {
            NodeList children = parent.getChildNodes();

            Map<String, String> params = new HashMap<String, String>();
            List<RuntimeBeanReference> configurators = new ManagedList<RuntimeBeanReference>();

            configurators.add(new RuntimeBeanReference(DEFAULT_SPRING_CONFIGURATOR_ID));

            for (int i = 0; i < children.getLength(); i++) {
                Node node = children.item(i);

                if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.COMMENT_NODE) {
                    continue;
                }

                Element child = (Element) node;

                if ("config-param".equals(child.getLocalName())) {
                    String paramName = child.getAttribute("name");
                    String value = child.getAttribute("value");
                    params.put(paramName, value);
                } else if ("configurator".equals(child.getLocalName())) {
                    String refName = child.getAttribute("ref");

                    configurators.add(new RuntimeBeanReference(refName));
                }
            }

            dwrControllerDefinition.addPropertyValue("configParams", params);
            dwrControllerDefinition.addPropertyValue("configurators", configurators);

        }
    }
}
